package nl.littlerobots.bean.internal.serial;
import android.bluetooth.BluetoothGatt;
import android.bluetooth.BluetoothGattCharacteristic;
import android.bluetooth.BluetoothGattDescriptor;
import android.bluetooth.BluetoothGattService;
import android.os.Handler;
import android.os.Looper;
import android.util.Log;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.UUID;
import nl.littlerobots.bean.internal.ble.BaseProfile;
import nl.littlerobots.bean.internal.ble.GattClient;
import nl.littlerobots.beanlib.BuildConfig;
import okio.Buffer;
public class GattSerialTransportProfile extends BaseProfile {
public static final int PACKET_TX_MAX_PAYLOAD_LENGTH = 19;
private static final String TAG = "GattSerialTransportProfile";
private static final UUID BEAN_SERIAL_CHARACTERISTIC_UUID = UUID.fromString("a495ff11-c5b1-4b44-b512-1370f02d74de");
private static final UUID BEAN_SERIAL_SERVICE_UUID = UUID.fromString("a495ff10-c5b1-4b44-b512-1370f02d74de");
private static final UUID BEAN_SCRATCH_SERVICE_UUID = UUID.fromString("a495ff20-c5b1-4b44-b512-1370f02d74de");
private static final UUID BEAN_SCRATCH_0_UUID = UUID.fromString("a495ff21-c5b1-4b44-b512-1370f02d74de");
private static final UUID BEAN_SCRATCH_1_UUID = UUID.fromString("a495ff22-c5b1-4b44-b512-1370f02d74de");
private static final UUID BEAN_SCRATCH_2_UUID = UUID.fromString("a495ff23-c5b1-4b44-b512-1370f02d74de");
private static final UUID BEAN_SCRATCH_3_UUID = UUID.fromString("a495ff24-c5b1-4b44-b512-1370f02d74de");
private static final UUID BEAN_SCRATCH_4_UUID = UUID.fromString("a495ff25-c5b1-4b44-b512-1370f02d74de");
private static final List<UUID> BEAN_SCRATCH_UUIDS = Arrays.asList(BEAN_SCRATCH_0_UUID, BEAN_SCRATCH_1_UUID, BEAN_SCRATCH_2_UUID, BEAN_SCRATCH_3_UUID, BEAN_SCRATCH_4_UUID);
private WeakReference<Listener> mListener = new WeakReference<>(null);
private BluetoothGattCharacteristic mSerialCharacteristic;
private boolean mReadyToSend = false;
private final Runnable mDequeueRunnable = new Runnable() {
@Override
public void run() {
if (!mPendingPackets.isEmpty()) {
if (mReadyToSend && mSerialCharacteristic != null) {
mReadyToSend = false;
GattSerialPacket packet = mPendingPackets.remove(0);
mSerialCharacteristic.setValue(packet.getPacketData());
mGattClient.writeCharacteristic(mSerialCharacteristic);
}
mHandler.postDelayed(this, 150);
}
}
};
private List<GattSerialPacket> mPendingPackets = new ArrayList<>(32);
private Handler mHandler = new Handler(Looper.getMainLooper());
private int mOutgoingMessageCount = 0;
private MessageAssembler mMessageAssembler = new MessageAssembler();
public GattSerialTransportProfile(GattClient client) {
super(client);
}
@Override
public void onConnectionStateChange(int newState) {
if (newState == BluetoothGatt.STATE_CONNECTED) {
mGattClient.discoverServices();
} else if (newState == BluetoothGatt.STATE_DISCONNECTED) {
abort();
}
}
@Override
public void onServicesDiscovered(GattClient client) {
BluetoothGattService service = client.getService(BEAN_SERIAL_SERVICE_UUID);
mSerialCharacteristic = service.getCharacteristic(BEAN_SERIAL_CHARACTERISTIC_UUID);
if (mSerialCharacteristic == null) {
Log.w(TAG, "Did not find bean serial on device, disconnecting");
abort();
} else {
if (BuildConfig.DEBUG) {
Log.i(TAG, "Connected");
}
client.setCharacteristicNotification(mSerialCharacteristic, true);
for (BluetoothGattDescriptor descriptor : mSerialCharacteristic.getDescriptors()) {
if ((descriptor.getUuid().getMostSignificantBits() >> 32) == 0x2902) {
descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);
client.writeDescriptor(descriptor);
}
}
service = client.getService(BEAN_SCRATCH_SERVICE_UUID);
for (BluetoothGattCharacteristic characteristic : service.getCharacteristics()) {
client.setCharacteristicNotification(characteristic, true);
for (BluetoothGattDescriptor descriptor : characteristic.getDescriptors()) {
if ((descriptor.getUuid().getMostSignificantBits() >> 32) == 0x2902) {
descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);
client.writeDescriptor(descriptor);
}
}
}
}
}
@Override
public void onCharacteristicWrite(GattClient client, BluetoothGattCharacteristic characteristic) {
if (mSerialCharacteristic == characteristic) {
mHandler.removeCallbacks(mDequeueRunnable);
mReadyToSend = true;
mHandler.post(mDequeueRunnable);
}
}
@Override
public void onCharacteristicChanged(GattClient client, BluetoothGattCharacteristic characteristic) {
if (characteristic == mSerialCharacteristic) {
byte[] data = mMessageAssembler.assemble(new GattSerialPacket(characteristic.getValue()));
if (data != null) {
if (BuildConfig.DEBUG) {
Log.d(TAG, "Received data");
}
Listener listener = mListener.get();
if (listener != null) {
listener.onMessageReceived(data);
} else {
client.disconnect();
}
}
} else {
// scratch
int index = BEAN_SCRATCH_UUIDS.indexOf(characteristic.getUuid());
if (index > -1) {
if (BuildConfig.DEBUG) {
Log.d(TAG, "Received scratch bank update (" + index + ")");
}
Listener listener = mListener.get();
if (listener != null) {
listener.onScratchValueChanged(index, characteristic.getValue());
} else {
client.disconnect();
}
}
}
}
@Override
public void onDescriptorWrite(GattClient client, BluetoothGattDescriptor descriptor) {
if (Arrays.equals(descriptor.getValue(), BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE)) {
if (descriptor.getCharacteristic() == mSerialCharacteristic) {
// connection has completed
mMessageAssembler.reset();
mReadyToSend = true;
mOutgoingMessageCount = 0;
if (BuildConfig.DEBUG) {
Log.i(TAG, "Setup complete");
}
Listener listener = mListener.get();
if (listener != null) {
listener.onConnected();
} else {
Log.e(TAG, "No listener, this must be a stale connection --> disconnect");
abort();
}
}
}
}
private void abort() {
boolean wasConnected = mSerialCharacteristic != null;
mSerialCharacteristic = null;
Listener listener = mListener.get();
if (listener != null) {
if (wasConnected) {
listener.onDisconnected();
} else {
listener.onConnectionFailed();
}
}
}
public void sendMessage(Buffer message) {
// create packet, add to queue, schedule
if (mSerialCharacteristic == null) {
//TODO maybe don't throw here since disconnection could have raced us
throw new IllegalStateException("Not connected");
}
int packets = (int) (message.size() / PACKET_TX_MAX_PAYLOAD_LENGTH);
mOutgoingMessageCount = (mOutgoingMessageCount + 1) % 4;
int size = (int) message.size();
for (int i = 0; i < size; i += PACKET_TX_MAX_PAYLOAD_LENGTH) {
GattSerialPacket packet = new GattSerialPacket(i == 0, mOutgoingMessageCount, packets--, message);
mPendingPackets.add(packet);
}
mHandler.post(mDequeueRunnable);
}
public void setListener(Listener listener) {
this.mListener = new WeakReference<>(listener);
}
// This listener is only for communicating with the Bean class
public static interface Listener {
public void onConnected();
public void onConnectionFailed();
public void onDisconnected();
public void onMessageReceived(byte[] data);
public void onScratchValueChanged(int bank, byte[] value);
}
}